/*
 * Copyright (c) 2012-2020 MIRACL UK Ltd.
 *
 * This file is part of MIRACL Core
 * (see https://github.com/miracl/core).
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

//
//  rand.swift
//
//  Created by Michael Scott on 17/06/2015.
//  Copyright (c) 2015 Michael Scott. All rights reserved.
//
//  Cryptographic strong random number generator

/* Marsaglia & Zaman Random number generator constants */
public struct RAND {
    private static let NK:Int=21
    private static let NJ:Int=6
    private static let NV:Int=8
    private var ira=[UInt32](repeating: 0,count: NK)
    private var rndptr:Int=0
    private var borrow:UInt32=0
    private var pool_ptr:Int=0
    private var pool=[UInt8](repeating: 0,count: 32)
    
    public mutating func clean()
    {
        pool_ptr=0
        rndptr=0
        for i in 0 ..< 32 {pool[i]=0}
        for i in 0 ..< RAND.NK {ira[i]=0}
        borrow=0;
    }
    
    public init() {clean()}
    
    private mutating func sbrand() -> UInt32
    { /* Marsaglia & Zaman random number generator */
        rndptr+=1;
        if rndptr<RAND.NK {return ira[rndptr]}
        rndptr=0;
        var k=RAND.NK-RAND.NJ
        for i in 0 ..< RAND.NK
        {
            if k==RAND.NK {k=0}
            let t=ira[k];
            let pdiff=t &- ira[i] &- borrow
            if pdiff<t {borrow=0}
            if pdiff>t {borrow=1}
            ira[i]=pdiff
            k += 1;
        }
        return ira[0]
    }
    
    mutating func sirand(_ seed: UInt32)
    {
        var m:UInt32=1
        var s:UInt32=seed
        borrow=0;
        rndptr=0
        ira[0]^=s
        for i in 1 ..< RAND.NK
        { /* fill initialisation vector */
            let ipn=(RAND.NV*i)%RAND.NK
            ira[ipn]^=m
            let t=m
            m=s &- m
            s=t
        }
        for _ in 0 ..< 10000 {_ = sbrand()}
    }
    
    private mutating func fill_pool()
    {
        var sh=HASH256()
        for _ in 0 ..< 128 {sh.process(UInt8(sbrand()&0xff))}
        pool=sh.hash()
        pool_ptr=0
    }
    
    private mutating func pack(_ b: [UInt8]) -> UInt32
    {
	var r=UInt32(b[3])<<24
	r |=  UInt32(b[2])<<16
	r |=  UInt32(b[1])<<8
	r |=  UInt32(b[0])
	return r
        //return (UInt32(b[3])<<24)|(UInt32(b[2])<<16)|(UInt32(b[1])<<8)|(UInt32(b[0]))
    }
  
/* Initialize RNG with some real entropy from some external source */
    public mutating func seed(_ rawlen: Int,_ raw: [UInt8])
    { /* initialise from at least 128 byte string of raw random entropy */
        var digest=[UInt8]()
        var b=[UInt8](repeating: 0, count: 4)
        var sh=HASH256()
        pool_ptr=0
        for i in 0 ..< RAND.NK {ira[i]=0}
        if rawlen>0
        {
            for i in 0 ..< rawlen {sh.process(raw[i])}
            digest=sh.hash()
            
            for i in 0 ..< 8
            {
                b[0]=digest[4*i]; b[1]=digest[4*i+1]; b[2]=digest[4*i+2]; b[3]=digest[4*i+3]
                sirand(pack(b))
            }
            
        }
        fill_pool()
    }
    
    public mutating func getByte() -> UInt8
    {
        let r=pool[pool_ptr]; pool_ptr+=1
        if pool_ptr>=32 {fill_pool()}
        return r
    }
    
    
}
